使用memmove/memcpy库函数拷贝内存时容易产生的异常 |
您所在的位置:网站首页 › c 空指针异常 › 使用memmove/memcpy库函数拷贝内存时容易产生的异常 |
使用memmove/memcpy库函数拷贝内存时容易引发的异常
首先,我们来看一下C库函数memmove的原型,如下: void memmove( void dest, const void* src, size_t n); 头文件: 功能:由src所指内存区域复制n个字节到dest所指内存区域。 返回值:函数返回指向dest的指针。 其次,C库函数memcpy的原型如下: void *memcpy(void *dest, const void *src, size_t n); 头文件: 功能:由src所指内存区域复制n个字节到dest所指内存区域。 返回值:函数返回指向dest的指针。 然后,简单描述一下上述两个库函数的区别: 当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。 内存不重叠情况: 言归正传,下面我们来探讨一下使用memmove/memcpy库函数拷贝内存时容易引发的异常 首先,先看下面的一段简单的代码: #include #include class Base { public: int base_id = 0; std::string base_name; float * depths = nullptr; Base() {} virtual ~Base() { delete[] depths; depths = nullptr; } int GetBaseId() { return base_id; } std::string GetBaseName() { return base_name; } float * GetDepths() { return depths; } virtual void ChangeBaseName() { base_name = "base name"; } }; class BaseInfo : public Base { public: int baseinfo_id = 0; std::string baseinfo_name; Base() {} ~Base() {} }; int main() { BaseInfo * binfo1 = new BaseInfo[2]; binfo1[0].baseinfo_id = 1; binfo1[0].baseinfo_name = "baseinfo 1"; binfo1[0].depths = new float[3] {0.4f, 0.7f, 0.3f}; binfo1[1].baseinfo_id = 2; binfo1[1].baseinfo_name = "baseinfo 2"; binfo1[1].depths = new float[2] {0.9f, 0.7f}; BaseInfo * binfo2 = new BaseInfo[2]; memmove(binfo2, binfo1, 2 * sizeof(class BaseInfo)); delete[] binfo1; binfo1 = nullptr; delete[] binfo2; binfo2 = nullptr; return 0; } 调试上面的这段代码,当程序运行到“delete[] binfo2;”这一行时,程序中断发生错误,抛出异常如下: 1" memmove函数是用来拷贝内存的,我首先想到的是【src源地址内存和dest目的地址的内存有重叠,导致重复释放同一块内存区域产生异常】,经过添加代码,验证两块内存并没有重叠部分,遂排除此种可能性。 2" 其次我想到的是【BaseInfo里的成员变量包含指针,两个BaseInfo对象的指针成员指向同一块内存区域,析构对象时导致重复释放同一块内存区域产生异常】。 回到代码中可以看出BaseInfo从基类Base继承了一个指针变量“depths”,看下图:depths(3)/depth(4)是通过拷贝了depths(1)/depths(2)的内存得来的,因此depths(1)的值等于depths(3)的值,即这两个数组指针指向内存中同一块内存区域(同理,depths(2)的值等于depths(4)的值。所以当我们delete[] baseinfo1时调用析构函数时第一次释放了depths(1)所指向的内存,再delete[] baseinfo2时调用析构函数时会第二次去释放depths(3)所指向的内存,重复释放内存导致异常。 4" 引发异常的另一个原因有时候很难想到,原因是【BaseInfo的成员中包含std::string类型的变量】。我们下面来看一下string的原型,它用来表示字符串,但实质上它是一个类。string中含有一个m_data的指针变量,所以也会导致重复释放同一块内存的异常,原理同2"相似。 class String { public: String(const char *str = NULL); // 普通构造 String(const String &other); // 拷贝构造函数 ~ String(void); // 析构函数 String & operate =(const String &other); // 赋值函数 private: char *m_data; // 用于保存字符串 };5" 最后关于【虚指针的释放】: 一个存在虚函数的类会有一个虚表,这个类的对象都包含一个成员即虚指针,这些虚指针都指向这个类的虚表,即同类的对象共享这个类的虚表。虚表相当于一个一维数组,它里面按顺序存放这个类里每个虚函数实现的地址。在构造函数中进行虚表的创建以及虚指针的初始化,而虚指针的释放我也没太弄清楚,不知道它是在什么时候如何释放的。 我的猜想是: 在构造A类的第一个对象时构造了这个类的虚表,并将这个对象的虚指针初始化为这个虚表,之后再构造A类对象时将它们的虚指针都初始化为此虚表,所有A类对象共享A类的虚表;在析构A类的对象时只是将虚指针置空,直到析构最后一个A类的对象时才将它的虚指针指向的虚表从内存中释放掉,所以上述的程序才没有导致重复释放内存的异常。(貌似有点道理,好像又有点牵强,哈哈) 我是个小神女,快来关注我吧~ |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |